"""
OAuth 2.0 请求认证中间件
"""
import logging
from fnmatch import fnmatch

from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.types import ASGIApp, Receive, Scope, Send

from axiom_boot.api.models import error_response
from axiom_boot.auth.context import clear_principal_context, set_current_principal
from axiom_boot.auth.models import Principal
from axiom_boot.auth.services.token_service import (CredentialsException,
                                                    TokenService)
from axiom_boot.cache.service import CacheService

logger = logging.getLogger(__name__)


class OAuthMiddleware:
    """
    一个 ASGI 中间件，用于验证传入请求的 Bearer Token。
    它在处理每个请求时动态地从应用上下文中解析依赖项。
    """
    _whitelist: set[str] | None = None

    def __init__(
        self,
        app: ASGIApp,
    ):
        self.app = app

    def _get_whitelist(self, scope: Scope) -> set[str]:
        """
        延迟初始化白名单，以避免在应用启动时产生依赖。
        它在第一次需要时创建白名单，然后缓存结果。
        """
        if self._whitelist is None:
            settings = scope["app"].context._settings
            # 使用 set 来自动处理重复项
            whitelist = set(settings.oauth_whitelist or [])
            # 确保 token 端点和 API 文档始终在白名单内
            whitelist.add("/oauth/token")
            whitelist.add("/openapi.json")
            whitelist.add("/docs")
            whitelist.add("/docs/oauth2-redirect")
            whitelist.add("/redoc")
            self._whitelist = whitelist
        return self._whitelist

    async def __call__(self, scope: Scope, receive: Receive, send: Send):
        if scope["type"] != "http":
            await self.app(scope, receive, send)
            return

        request = Request(scope, receive)

        # 检查路径是否在白名单内
        whitelist = self._get_whitelist(scope)
        if any(fnmatch(request.url.path, pattern) for pattern in whitelist):
            await self.app(scope, receive, send)
            return

        # 从请求头获取令牌
        auth_header = request.headers.get("Authorization")

        if not auth_header or not auth_header.lower().startswith("bearer "):
            response = JSONResponse(
                status_code=401,
                content=error_response(code=401, message="认证请求头缺失或格式无效").model_dump(),
                headers={"WWW-Authenticate": "Bearer"},
            )
            await response(scope, receive, send)
            return

        token = auth_header.split(" ")[1]

        try:
            # 在请求开始时设置上下文，在结束时用 finally 清理
            set_current_principal(None)

            app = scope["app"]
            token_service: TokenService = await app.context.get(TokenService)
            cache_service: CacheService = await app.context.get(CacheService)

            # 1. 验证 Token 签名和有效期, 并检查 JTI 黑名单
            payload = await token_service.decode_token(token)

            # 2. 【简化】不再在此处进行 scope 或缓存对比，这些职责已移至 security 依赖和业务逻辑层
            #    以保证单一职责原则。中间件的核心任务是验证令牌本身并将 principal 附加到请求。
            
            # 3. 创建 Principal 对象用于当前请求
            principal = Principal.model_validate(payload)

            # 4. 将 Principal 同时设置到 request.state 和上下文中，供后续依赖使用
            request.state.principal = principal
            request.state.permissions = principal.permissions # 确保权限也被传递
            set_current_principal(principal)

            logger.info(f"【认证中间件】: 用户 '{principal.username}' (ID: {principal.user_id}) 的令牌验证通过。")
            
            await self.app(scope, receive, send)

        except CredentialsException as e:
            status_code = getattr(e, 'status_code', 401)
            response_model = error_response(code=status_code, message=str(e) or "认证失败")
            headers = {}
            if status_code == 401:
                headers["WWW-Authenticate"] = "Bearer"
            
            response = JSONResponse(
                status_code=status_code,
                content=response_model.model_dump(),
                headers=headers,
            )
            await response(scope, receive, send)
            return
        except Exception as e:
            logger.error(f"【认证中间件】: 发生未预料的内部错误。错误: {e}", exc_info=True)
            response_model = error_response(code=500, message="认证时发生内部错误")
            response = JSONResponse(status_code=500, content=response_model.model_dump())
            await response(scope, receive, send)
            return
        finally:
            clear_principal_context()
            logger.info("【认证中间件】: 请求处理完毕，已清理安全上下文。")
